#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)

# This file is part of Cockpit.
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <https://www.gnu.org/licenses/>.

import storagelib
import testlib


@testlib.nondestructive
class TestStoragePartitions(storagelib.StorageCase):

    def testPartitions(self):
        b = self.browser

        self.login_and_go("/storage")

        # A loopback device ends with a digit and partitions have
        # names like "/dev/loop0p1".  Check that the storage stack has
        # no difficulties with that.
        #
        # We are especially careful to use a device name that doesn't
        # end in all zeros, because that would be too easy and
        # wouldn't trigger this bug:
        #
        # https://github.com/storaged-project/storaged/issues/97

        dev = self.add_loopback_disk(10, "loop12")
        self.click_card_row("Storage", name=dev)

        self.click_card_dropdown("Block device", "Create partition table")
        self.dialog({"type": "gpt"})
        b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")

        self.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")
        self.dialog({"type": "ext4",
                     "mount_point": f"{self.mnt_dir}/foo"})
        b.wait_text(self.card_row_col("GPT partitions", 1, 3), f"{self.mnt_dir}/foo")

        self.click_card_row("GPT partitions", 1)
        self.click_card_dropdown("Partition", "Delete")
        self.confirm()
        b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")

    def testSizeSlider(self):
        m = self.machine
        b = self.browser

        self.login_and_go("/storage")

        disk = self.add_ram_disk()
        self.click_card_row("Storage", name=disk)

        self.click_card_dropdown("Solid State Drive", "Create partition table")
        self.dialog({"type": "gpt"})
        b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")

        self.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")
        self.dialog_wait_open()
        self.dialog_set_val("type", "empty")

        slider = self.dialog_field("size") + " .pf-v6-c-slider .pf-v6-c-slider__rail"

        # Move the slider one pixel past the middle, this should give a fractional size.
        # See https://github.com/cockpit-project/cockpit/pull/10968 for more about this.
        width = b.call_js_func('(function (sel) { return ph_find(sel).offsetWidth; })', slider)
        about_half_way = width / 2 + 1

        b.mouse(slider, "click", about_half_way, 0)
        self.dialog_wait_val("size", 27.3)
        b.focus(slider + " + .pf-v6-c-slider__thumb")
        b.key("ArrowLeft", 5)
        self.dialog_wait_val("size", 26.2)

        # Check that changing units doesn't affect the text input
        unit = "1000000000"
        b.select_from_dropdown(".size-unit > select", unit)
        self.dialog_wait_val("size", 26.2, unit)

        # Switch the dialog into the mode where it stores separate text and unit
        # instead of a numeric value.
        b.set_input_text(".size-text > input", "26.200001")
        b.select_from_dropdown(".size-unit > select", "1000000000")  # GB

        # Apply the dialog. This will convert from text+unit back to a
        # correctly rounded numeric value (which is too large).

        self.dialog_apply()
        self.dialog_wait_error("size", "Size is too large")

        # The text in the input field must not have changed
        b.wait_val(".size-text > input", "26.200001")

        # Change unit back to MB.  (This must round again, which it
        # didn't always do.)
        b.select_from_dropdown(".size-unit > select", "1000000")  # MB

        self.dialog_apply()
        self.dialog_wait_close()

        testlib.wait(lambda: m.execute(f"lsblk -no SIZE {disk}1").strip() == "25M")

    def testResize(self):
        m = self.machine
        b = self.browser

        self.login_and_go("/storage")

        disk = self.add_ram_disk(100)
        self.click_card_row("Storage", name=disk)

        self.click_card_dropdown("Solid State Drive", "Create partition table")
        self.dialog({"type": "gpt"})
        b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")

        # Make two partitions that cover the whole disk.

        self.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")
        self.dialog({"type": "ext4",
                     "mount_point": f"{self.mnt_dir}/foo1",
                     "size": 80},
                    secondary=True)
        self.click_dropdown(self.card_row("GPT partitions", 2), "Create partition")
        self.dialog({"type": "ext4",
                     "mount_point": f"{self.mnt_dir}/foo2",
                     "size": 23},
                    secondary=True)

        b.wait_text(self.card_row_col("GPT partitions", 1, 4), "79.7 MB")
        b.wait_text(self.card_row_col("GPT partitions", 2, 4), "23.1 MB")

        # Shrink the first
        self.click_card_row("GPT partitions", 1)
        b.click(self.card_button("Partition", "Shrink"))
        self.dialog({"size": 50})
        b.wait_in_text(self.card_desc("Partition", "Size"), "50.3 MB")

        # Grow it back externally, Cockpit should complain.  Shrink it
        # again with Cockpit.
        m.execute(f"parted -s {disk} resizepart 1 80.7MB")
        b.click(self.card_button("Partition", "Shrink partition"))
        b.wait_in_text(self.card_desc("Partition", "Size"), "50.3 MB")

        # Grow it back externally again. Grow the filesystem with
        # Cockpit.
        m.execute(f"parted -s {disk} resizepart 1 80.7MB")
        b.click(self.card_button("Partition", "Grow content"))
        b.wait_in_text(self.card_desc("Partition", "Size"), "79.7 MB")
        b.wait_visible(self.card_button("Partition", "Grow") + ":disabled")

        # Delete second partition and grow the first to take all the
        # space.
        b.click(self.card_parent_link())
        self.click_card_row("GPT partitions", 2)
        self.click_card_dropdown("Partition", "Delete")
        self.confirm()
        self.click_card_row("GPT partitions", location=f"{self.mnt_dir}/foo1 (not mounted)")
        b.click(self.card_button("Partition", "Grow"))
        self.dialog({"size": 103})
        b.wait_visible(self.card_button("Partition", "Grow") + ":disabled")
        b.wait_in_text(self.card_desc("Partition", "Size"), "103 MB")

    def testType(self):
        b = self.browser

        self.login_and_go("/storage")

        disk = self.add_ram_disk(100)
        self.click_card_row("Storage", name=disk)

        # GPT

        self.click_card_dropdown("Solid State Drive", "Create partition table")
        self.dialog({"type": "gpt"})
        b.wait_text(self.card_row_col("GPT partitions", 1, 1), "Free space")

        self.click_dropdown(self.card_row("GPT partitions", 1), "Create partition")
        self.dialog({"type": "empty"})

        self.click_card_row("GPT partitions", 1)
        b.wait_text(self.card_desc("Partition", "Type"), "Linux filesystem data")
        b.click(self.card_desc_action("Partition", "Type"))
        self.dialog({"type": "c12a7328-f81f-11d2-ba4b-00a0c93ec93b"})
        b.wait_text(self.card_desc("Partition", "Type"), "EFI system partition")
        b.click(self.card_desc_action("Partition", "Type"))
        self.dialog_wait_open()
        self.dialog_set_val("type", "custom")
        self.dialog_set_val("custom", "bla bla")
        self.dialog_apply()
        self.dialog_wait_error("custom", "Type can only contain the characters 0 to 9, A to F, and \"-\".")
        self.dialog_set_val("custom", "7D0359A3-02B3-4F0A865C-654403E70625")
        self.dialog_apply()
        self.dialog_wait_error("custom", "Type must be of the form NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN.")
        self.dialog_set_val("custom", "7D0359A3-02B3-4F0A-865C-654403E70625")
        self.dialog_apply()
        self.dialog_wait_close()
        b.wait_text(self.card_desc("Partition", "Type"), "7d0359a3-02b3-4f0a-865c-654403e70625")

        # DOS

        b.click(self.card_parent_link())
        self.click_card_dropdown("Solid State Drive", "Create partition table")
        self.dialog({"type": "dos"})
        b.wait_text(self.card_row_col("DOS partitions", 1, 1), "Free space")

        self.click_dropdown(self.card_row("DOS partitions", 1), "Create partition")
        self.dialog({"size": 100, "type": "empty"})

        self.click_card_row("DOS partitions", 1)
        b.wait_text(self.card_desc("Partition", "Type"), "Linux filesystem data")
        b.click(self.card_desc_action("Partition", "Type"))
        self.dialog({"type": "ef"})
        b.wait_text(self.card_desc("Partition", "Type"), "EFI system partition")
        b.click(self.card_desc_action("Partition", "Type"))
        self.dialog_wait_open()
        self.dialog_set_val("type", "custom")
        self.dialog_set_val("custom", "bla bla")
        self.dialog_apply()
        self.dialog_wait_error("custom", "Type must contain exactly two hexadecimal characters (0 to 9, A to F).")
        self.dialog_set_val("custom", "C8")
        self.dialog_apply()
        self.dialog_wait_close()
        b.wait_text(self.card_desc("Partition", "Type"), "c8")


if __name__ == '__main__':
    testlib.test_main()
